class BigInteger {
    static valueOf(str) {
        return new BigInteger(`${str}`);
    }

    constructor(numStr) {
        // remove zeros before numbers
        this.removeBeforeZeros = (x) => {
            while(x[0] == 0 && x.length > 1) x.shift();
            return x;
        }
        // to numbers list
        this.toNumbers = (x) => this.removeBeforeZeros(x.split("").map(x => x.charCodeAt() - 48));
        // fill zeros before numbers
        this.fillZeros = (arr, len) => new Array(len - arr.length).fill(0).concat(arr);
        // init numbers
        this.initProcess = (x) => {
            let sign = "+"
            for(let i=0; i < x.length; ++i) {
                switch(x[i]) {
                    case "-": 
                        sign = sign == "-"? "+": "-";
                        break;
                    case "+":
                        if(sign == "-") throw new Error("operating sign error.");
                        break;
                    default:
                        let val = numStr.substring(i);
                        if(val.length <= 0) throw new Error("has no numbers.");
                        if(!/^[0-9]+$/.test(val)) throw new Error("string isn't big integer.");
                        return [sign == "+"? 1: -1, this.toNumbers(val)];
                }
            }
        }
        // swap short integer and long integer
        this.swap = (a, nums1, b, nums2) => {
            if(b.length > a.length) {
                let temp = nums1;
                nums1 = nums2;
                nums2 = temp;
            }
            return [nums1, nums2];
        } 
        // get environment of calculation
        this.calEnvir = (bigInteger, withNoSwap = false) => {
            let calLen = Math.max(this.length, bigInteger.length),
            n1 = this.fillZeros(this.value, calLen),
            n2 = this.fillZeros(bigInteger.value, calLen);
            if(withNoSwap) return [n1, n2, calLen];
            return this.swap(this, n1, bigInteger, n2).concat(calLen);
        };
        // base parameter initialize
        let [sign, value] = this.initProcess(numStr);
        this.value = value;
        this.sign = sign;
    }

    copy(unsigned = false) {
        if(unsigned)
            return new BigInteger(this.value.join(""));
        else
            return new BigInteger(`${this.sign > 0? "+": "-"}${this.value.join("")}`);
    }

    get positive() {
        return this.sign > 0;
    }

    get negative() {
        return !this.positive;
    }

    get length() {
        return this.value.length;
    }

    sub(bigInteger) {
        if(!(bigInteger instanceof BigInteger))
            throw new Error("class type mismatch.");
        
        if(this.positive && bigInteger.negative) {
            return this.add(bigInteger.copy(true));
        } else if(this.negative && bigInteger.positive) {
            return bigInteger.sub(this.copy(true));
        } else if(this.negative && bigInteger.negative) {
            return bigInteger.copy(true).sub(this.copy(true));
        }

        let [nums1, nums2, calLen] = this.calEnvir(bigInteger),
            calQueue = new Array(calLen).fill(0),
            turn = 1;

        // 迭代向前借位
        function rent(beforeIndex) {
            for(let j=beforeIndex; j >= 0; --j) {
                if(calQueue[j] > 0) {
                    calQueue[j] -= 1;
                    for(let k=j+1; k <= beforeIndex; ++k) calQueue[k] = 9;
                    return true;
                }
            }
            return false;
        }

        for(let i=0; i < calLen; ++i) {
            let value = (nums1[i] - nums2[i]) * turn;
            if(value >= 0) {
                calQueue[i] = value;
            } else if(rent(i - 1)) {
                calQueue[i] = 10 + value;
            } else {
                calQueue[i] = -value;
                turn = -turn;
            }
        }

        return new BigInteger(`${turn > 0? "": "-"}${calQueue.join("")}`);
    }

    add(bigInteger) {
        if(!(bigInteger instanceof BigInteger))
            throw new Error("class type mismatch.");

        if(this.positive != bigInteger.positive) {
            return this.positive? this.sub(bigInteger.copy(true)): bigInteger.sub(this.copy(true));
        }

        let [nums1, nums2, calLen] = this.calEnvir(bigInteger),
            calQueue = new Array(calLen).fill(0),
            carry = 0;
        
        for(let i=calLen-1; i >= 0; --i) {
            let value = nums1[i] + nums2[i] + carry;
            calQueue[i] = value % 10;
            carry = value / 10 | 0;
        }

        return new BigInteger(`${this.positive? "": "-"}${[carry].concat(calQueue).join("")}`);
    }

    multi(bigInteger) {
        if(!(bigInteger instanceof BigInteger))
            throw new Error("class type mismatch.");

        let [nums1, nums2, calLen] = this.calEnvir(bigInteger),
            calQueue = new Array(calLen).fill(0),
            result = [];
    
        for(let i=calLen - 1; i >= 0; --i) {
            let carry = 0;
            if(nums2[i] > 0) {
                for(let j=calLen - 1; j >= 0; --j) {
                    let value = calQueue[j] + nums1[j] * nums2[i] + carry;
                    calQueue[j] = value % 10;
                    carry = value / 10 | 0;
                }
            }
            result.push(calQueue.pop());
            calQueue = [carry].concat(calQueue);
        }
        result = calQueue.concat(result.reverse());
        return new BigInteger(`${this.sign * bigInteger.sign > 0? "+": "-"}${result.join("")}`);
    }

    pow(power) {
        if(power < 0) throw new Error("can't calculate ");
        if(power == 0) return BigInteger.valueOf(1);

        let result = this;
        for(let i=1; i < power; ++i) result = result.multi(this);
        return result;
    }

    compare(bigInteger) {
        if(!(bigInteger instanceof BigInteger))
            throw new Error("class type mismatch.");

        if(this.positive != bigInteger.positive) {
            return this.positive? 1: -1;
        }

        let [nums1, nums2, calLen] = this.calEnvir(bigInteger, true);
        for(let i=0; i < calLen; ++i) {
            if(nums1[i] > nums2[i]) {
                return this.positive? 1: -1;
            } else if(nums1[i] < nums2[i]) {
                return this.positive? -1: 1;
            }
        }

        return 0;
    }

    toString() {
        return (this.sign < 0? "-": "") + this.value.join("");
    }
}

exports.BigInteger = BigInteger;